
//
// commander.c
//
// Copyright (c) 2012 TJ Holowaychuk <tj@vision-media.ca>
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "commander.h"

/*
 * Output error and exit.
 */

static void
error(TCHAR *msg) {
  _ftprintf(stderr, _T("%s\n"), msg);
  exit(1);
}

/*
 * Output command version.
 */

static int
command_version(command_t *self, void* param) {
  _tprintf(_T("%s\n"), self->version);
  command_deinit(self);
  exit(0);
  return 1;
}

/*
 * Output command help.
 */

int
command_help(command_t *self, void* param) {
  int i;
  const TCHAR* name;
#ifdef WIN32
  int separator = '\\';
#else
  int separator = '/';
#endif
  name = _tcsrchr(self->name, separator);
  if(name==NULL || !*name)
	  name = self->name;
  else
	  name++;
  _tprintf(_T("\n"));
  _tprintf(_T("  Usage: %s %s\n"), name, self->usage);
  _tprintf(_T("\n"));
  _tprintf(_T("  Options:\n"));
  _tprintf(_T("\n"));
  for (i = 0; i < self->option_count; ++i) {
    command_option_t *option = &self->options[i];
    _tprintf(_T("    %s, %-25s %s\n")
      , option->small
      , option->large_with_arg
      , option->description);
  }
  _tprintf(_T("\n"));
  command_deinit(self);
  exit(0);
  return 1;
}

/*
 * Initialize with program `name` and `version`.
 */

void
command_init(command_t *self, const TCHAR *name, const TCHAR *version) {
  memset(self, 0, sizeof(*self));/*just in case if init will fail and some pointers will be filled with garbage*/
  self->arg = NULL;
  self->name = name;
  self->version = version;
  self->option_count = self->argc = 0;
  self->usage = _T("[options]");
  command_option(self, _T("-V"), _T("--version"), _T("output program version"), command_version, NULL);
  command_option(self, _T("-h"), _T("--help"), _T("output help information"), command_help, NULL);
}

/*
 * Parse argname from `str`. For example
 * Take "--required <arg>" and populate `flag`
 * with "--required" and `arg` with "<arg>".
 */

static void
parse_argname(const TCHAR *str, TCHAR *flag, TCHAR *arg) {
  int buffer = 0;
  unsigned int i;
  size_t flagpos = 0;
  size_t argpos = 0;
  size_t len = _tcslen(str);

  for (i = 0; i < len; ++i) {
    if (buffer || '[' == str[i] || '<' == str[i]) {
      buffer = 1;
      arg[argpos++] = str[i];
    } else {
      if (' ' == str[i]) continue;
      flag[flagpos++] = str[i];
    }
  }

  arg[argpos] = 0;
  flag[flagpos] = 0;
}

/*
 * Define an option.
 */

void
command_option(command_t *self, const TCHAR *small, const TCHAR *large, const TCHAR *desc, command_callback_t cb, void* param) {
  command_option_t *option;
  int n = self->option_count++;
  size_t str_large_bytes_num;
  if (n == COMMANDER_MAX_OPTIONS) {
    command_deinit(self);
    error(_T("Maximum option definitions exceeded"));
  }
  option = &self->options[n];
  option->cb = cb;
  option->small = small;
  option->description = desc;
  option->required_arg = option->optional_arg = 0;
  option->large_with_arg = large;
  str_large_bytes_num = (_tcslen(large) + 1)* sizeof(TCHAR);
  option->argname = malloc(str_large_bytes_num);
  assert(option->argname);
  option->large = malloc(str_large_bytes_num);
  assert(option->large);
  parse_argname(large, option->large, option->argname);
  if ('[' == option->argname[0]) option->optional_arg = 1;
  if ('<' == option->argname[0]) option->required_arg = 1;
  option->user_param = param;
  option->parse_result = 0;
}

/*
 * Parse `argv`.
 * 
 * TODO: list of short opts (-vo as -v -o)
 */

void
command_parse(command_t *self, int argc, TCHAR **argv) {
  int literal = 0;
  int i;
  int j;

  for (i = 1; i < argc; ++i) {
	int n;
    const TCHAR *arg = argv[i];
    for (j = 0; j < self->option_count; ++j) {
      command_option_t *option = &self->options[j];

      // match flag
      if (!_tcscmp(arg, option->small) || !_tcscmp(arg, option->large)) {
        self->arg = NULL;

        // required
        if (option->required_arg) {
          arg = argv[++i];
          if (!arg || '-' == arg[0]) {
            _ftprintf(stderr, _T("%s %s argument required\n"), option->large, option->argname);
			command_deinit(self);
            exit(1);
          }
          self->arg = arg;
        }

        // optional
        if (option->optional_arg) {
          if (argv[i + 1] && '-' != argv[i + 1][0]) {
            self->arg = argv[++i];
          }
        }

        // invoke callback
		option->parse_result = option->cb(self, option->user_param);
        goto match;
      }
    }

    // --
    if ('-' == arg[0] && '-' == arg[1] && 0 == arg[2]) {
      literal = 1;
      goto match;
    }

    // unrecognized
    if ('-' == arg[0] && !literal) {
      _ftprintf(stderr, _T("unrecognized flag %s\n"), arg);
	  command_deinit(self);
      exit(1);
    }

    n = self->argc++;
    if (n == COMMANDER_MAX_ARGS) {
      command_deinit(self);
      error(_T("Maximum number of arguments exceeded"));
	}
    self->argv[n] = (TCHAR *) arg;
    match:;
  }

  for (j = 0; j < self->option_count; j++) {
	  if (self->options[j].required_arg && !self->options[j].parse_result) {
		_ftprintf(stderr, _T("Not all required options are specified\n"));
		command_deinit(self);
        exit(1);
	  }
  }
}

void
command_deinit(command_t *self) {
  int j;
  for (j = 0; j < self->option_count; j++) {
    free(self->options[j].argname);
    free(self->options[j].large);
  }
  memset(self, 0, sizeof(*self));
}